package neuronet.server;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import neuronet.server.gui.ServerFrame;

import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;

/**
 * 
 * 
 * 
 * TODO: Eventuell auf NIO umstellen
 * (http://rox-xmlrpc.sourceforge.net/niotut/index.html)
 */

public class NeuroNetServer {

    // log4j-Logger
    private static Logger logger = Logger.getLogger(NeuroNetServer.class);

    // the socket used by the server
    private ServerSocket serverSocket;

    // default port
    private static int serverPort = 1504;
    private static String serverID = "Server mit Reis";
    private static String serverKey = "-1";
    private static String serverInfo = "Server zum Testen und für sonst was.";

    // Hashtable for clients
    private static Hashtable<String, ClientsTableItem> clientsTable = new Hashtable<String, ClientsTableItem>();

    // GUI
    private static ServerFrame gui;

    /**
     * Server constructor
     * 
     * @param port
     *            Serverport
     */
    public NeuroNetServer(int port)
    {
	serverPort = port;

	initGUI();

	addServerToClientTable();

	// gui = new ServerFrame();

	/* create serverSocket and wait for connection requests */
	try
	{
	    serverSocket = new ServerSocket(port);
	    logger.info("Server waiting for client on port " + serverSocket.getLocalPort());

	    NeuroNetServer.getGUI().addLogMessage("Server waiting for client on port " + serverSocket.getLocalPort());
	    getGUI().setServerData(serverSocket.getInetAddress().toString(), port);

	    while (true)
	    {
		logger.info("  [[[*****[[[[ -=O**+++**O=- ]]]]*****]]]");
		Socket socket = serverSocket.accept();
		logger.info("Client asked for a connection -> Startin Thread");
		TCPThread t = new TCPThread(socket);
		t.start();
		logger.info("  [[[*****[[[[ -=O**+++**O=- ]]]]*****]]]");
	    }
	}
	catch (IOException e)
	{
	    logger.error("Exception occured while running serverSocket: ", e);
	    getGUI().addLogMessage("Error: Exception occured while running serverSocket");

	}
    }

    /**
     * This method creates the GUI
     */
    private void initGUI()
    {
	// SwingUtilities.invokeLater(new Runnable()
	// {
	//
	// public void run()
	// {
	setGUI(new ServerFrame(this));
	// gui.setLocationRelativeTo(null);
	getGUI().setLocation(0, 0);
	getGUI().setVisible(true);
	// }
	// });

    }

    /**
     * Main method to run server as a console application
     * 
     * @param arg
     *            Commandline parameters in order: port
     */
    public static void main(String[] arg)
    {
	// Set up a simple configuration that logs on the console.
	BasicConfigurator.configure();

	// System.out.println("Server ist starting ...");
	logger.info("  [[[[[[[[[[[[[[ -=O=- ]]]]]]]]]]]]]]");
	logger.info(" Starting server, checking commandline arguments ...");

	// // Set up log4j configuration
	// PropertyConfigurator.configure("log4j.xml");

	// Check if arg[0] is defined
	if (arg.length > 0 && arg[0].length() > 0)
	{
	    try
	    {
		int serverPortTemp = Integer.valueOf(arg[0]);
		if (serverPortTemp > 0)
		    serverPort = serverPortTemp;
	    }
	    catch (NumberFormatException e)
	    {
		logger.error(" Illegal port argument!", e);
		e.printStackTrace();
	    }
	}

	// start server on defined port
	new NeuroNetServer(serverPort);
    }

    /**
     * This method add a new client (clientID and its clientTableIitem) to the
     * clientsTabele hashtable.
     * 
     * @param newClientID
     *            the clientID
     * @param newClientItem
     *            the corresponding clientItem containing the clients data
     */
    public static void addClientToTable(String newClientID, ClientsTableItem newClientItem)
    {
	NeuroNetServer.getGUI().addLogMessage("Adding client " + newClientID + " to table");
	clientsTable.put(newClientID, newClientItem);
	listAll();
    }

    /**
     * List all Clients in the clientsTable
     */
    private static void listAll()
    {
	Enumeration<String> keys = clientsTable.keys();
	int index = 0;
	logger.debug(" All clients in ClientsTable " + index);
	NeuroNetServer.getGUI().addLogMessage(" All clients in ClientsTable " + index);

	while (keys.hasMoreElements())
	{
	    index++;
	    String key = (String) keys.nextElement();

	    logger.debug(" " + index + ") " + key);
	    NeuroNetServer.getGUI().addLogMessage("|  " + index + ") " + key);
	}
	logger.debug(" #Elements " + index);
	NeuroNetServer.getGUI().addLogMessage("| #Elements " + index);
    }

    public static boolean isClientRegistered(String clientID)
    {
	return clientsTable.containsKey(clientID);
    }

    /**
     * This method removes an client form the clientsTable if and only if the
     * client is in the hashtable and if the clientKey is correct.
     * 
     * @param clientID
     *            the clientID
     * @param clientKey
     *            the corresponding clientKey
     * @return the status of the deletion
     */
    public static String removeClient(String clientID, String clientKey)
    {
	getGUI().addLogMessage("removing client " + clientID);
	String status = "";
	ClientsTableItem client = clientsTable.get(clientID);
	if (client != null)
	{
	    logger.info(" comparing : " + clientKey + " with " + client.getClientKey());
	    if (clientKey.equals(client.getClientKey()))
	    {

		if (clientsTable.remove(clientID) != null)
		    logger.info("client " + clientID + " logged off");
		else
		{
		    logger.info("client " + clientID + " NOT logged off");
		    listAll();
		    status = "status:error errorinfo:client_logoff_failed";
		    getGUI().addLogMessage(" -- " + status);
		    return status;
		}

		listAll();
		status = "status:ok";
		getGUI().addLogMessage(" -- " + status);
		return status;
	    }
	    else
	    {
		logger.error("status:error errorinfo:logoff_failed_clientkey_not_equal");
		status = "status:error errorinfo:logoff_failed_clientkey_not_equal";
		getGUI().addLogMessage(" -- " + status);
		return status;
	    }
	}
	status = "status:error errorinfo:logoff_failed_client_not_found";
	getGUI().addLogMessage(" -- " + status);
	return status;
    }

    /**
     * This method return a clientTableItem filled with the corresponding data
     * to the clientID.
     * 
     * @param clientID
     *            the clientID
     * @return the status of the deletion
     */
    public static ClientsTableItem getClientItem(String clientID)
    {
	ClientsTableItem client = clientsTable.get(clientID);
	if (client != null)
	{
	    // if (clientKey.equals(client.getClientKey()))
	    {
		logger.info("client " + clientID + " found");
		return client;
	    }
	    // else
	    // {
	    // logger.error("status:error errorinfo:client_not_found_-_clientkeys_not_equal");
	    // }
	}
	return null;
    }

    // TODO: Comment
    public static String sendMessage(String targetID, String senderID, String data)
    {
	ClientsTableItem client = clientsTable.get(targetID);
	if (client != null)
	{
	    String status = client.setMessage(senderID, data);
	    logger.info("message from " + senderID + " for " + targetID + " set");
	    return status;
	}
	else
	    return "status:error errorinfo:message_send_failed_-_client_not_found";
    }

    // TODO: Comment
    public Vector<String> getMessages(String clientID, String clientKey)
    {
	ClientsTableItem client = clientsTable.get(clientID);
	if (client != null)
	{
	    if (clientKey.equals(client.getClientKey()))
	    {
		logger.info("client " + clientID + " tries to get messages");
		return client.getMessages();
	    }
	    else
	    {
		logger.error("tatus:error errorinfo:getmessages_failed_clientkey_not_equal");
		return null;
	    }
	}
	logger.error("status:error errorinfo:logoff_failed_client_not_found");
	return null;
    }

    /**
     * This method resets the clientsTable by deleting all elements.
     * 
     * @return status
     */
    public static String reset()
    {
	clientsTable.clear();
	addServerToClientTable();
	return "status:ok";
    }

    private static void addServerToClientTable()
    {
	boolean regServer = NeuroNetServer.isClientRegistered(getServerID());

	logger.info("server " + getServerID() + " requests join.");

	logger.debug("isClientRegistered(serverID) returned " + regServer);
	if (!regServer)
	{
	    // add new client
	    logger.info("adding client " + getServerID() + "");
	    ClientsTableItem newServer = new ClientsTableItem(getServerID(), serverInfo);
	    NeuroNetServer.addClientToTable(getServerID(), newServer);
	    serverKey = newServer.getClientKey();
	}
	else
	{
	    logger.error("adding server " + getServerID() + " faild, server id already exists");
	}
    }

    /**
     * Method checks if client is authorized.
     * 
     * @return True/false if client ist authorized
     */
    public static boolean checkAuth(String clientID, String clientKey)
    {
	if (clientsTable == null)
	    return false;
	if (clientsTable.get(clientID) == null)
	    return false;
	if (clientID == null)
	    return false;
	if (clientKey == null)
	    return false;

	if (clientKey.equals(clientsTable.get(clientID).getClientKey()))
	    return true;
	return false;
    }

    public static Vector<String> getClientList()
    {
	Vector<String> back = new Vector<String>();
	Enumeration<String> days = clientsTable.keys();

	while (days.hasMoreElements())
	    back.add(days.nextElement());

	return back;
    }

    public static Vector<String> getTargetList(String clientid)
    {
	ClientsTableItem client = clientsTable.get(clientid);

	if (client == null)
	    return new Vector<String>();

	Vector<String> back = client.getDataTargets();

	return back;
    }

    /*
     * Gettter and Setter
     */

    public Hashtable<String, ClientsTableItem> getClientsTable()
    {
	return clientsTable;
    }

    public static void setGUI(ServerFrame gui)
    {
	NeuroNetServer.gui = gui;
    }

    public static ServerFrame getGUI()
    {
	return gui;
    }

    private static void setServerID(String serverID)
    {
	NeuroNetServer.serverID = serverID;
    }

    public static String getServerID()
    {
	return serverID;
    }
}